package com.teamfractal.fracdustry.common.blockentity.Generators;

import com.mojang.datafixers.DSL;
import com.teamfractal.fracdustry.common.block.impl.generators.FDVawtBlock;
import com.teamfractal.fracdustry.common.container.datasync.SimpleArray;
import com.teamfractal.fracdustry.common.sound.FDSounds;
import com.teamfractal.fracdustry.common.tools.CalculateWindPower;
import com.teamfractal.fracdustry.common.util.energystorage.FDEnergyStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.ObjectHolder;
import software.bernie.geckolib3.core.IAnimatable;
import software.bernie.geckolib3.core.PlayState;
import software.bernie.geckolib3.core.builder.AnimationBuilder;
import software.bernie.geckolib3.core.controller.AnimationController;
import software.bernie.geckolib3.core.event.predicate.AnimationEvent;
import software.bernie.geckolib3.core.manager.AnimationData;
import software.bernie.geckolib3.core.manager.AnimationFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicInteger;

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class FDVawtBlockEntity extends BlockEntity implements IAnimatable {
    public static final String NAME = "fracdustry:vawt";
    private final AnimationFactory factory = new AnimationFactory(this);

    @ObjectHolder(NAME)
    public static BlockEntityType<FDVawtBlockEntity> BLOCK_ENTITY_TYPE;

    private final FDEnergyStorage energyStorage = createEnergy();

    private final LazyOptional<FDEnergyStorage> energy = LazyOptional.of(() -> energyStorage);

    private final SimpleArray currentEnergy = new SimpleArray();
    private final SimpleArray maxEnergy = new SimpleArray();



    @SubscribeEvent
    public static void onRegisterBlockEntityType(@Nonnull RegistryEvent.Register<BlockEntityType<?>> event) {
        event.getRegistry().register(BlockEntityType.Builder.
                of(FDVawtBlockEntity::new, FDVawtBlock.BLOCK).build(DSL.remainderType()).setRegistryName(NAME));
    }


    //Disable capabilities when block is removed
    @Override
    public void setRemoved() {
        super.setRemoved();
        energy.invalidate();
    }



    //Block's energy generation and sound playing
    public void tickServer(BlockState state) {
        //todo:make energy generation adjustable in configs
        int e = level != null ? CalculateWindPower.calculateVawt(level, worldPosition) : 0;
        energyStorage.addEnergy(e);
        if (getBlockState().getValue(BlockStateProperties.POWERED) != e > 0)
        {level.setBlock(worldPosition
                , level.getBlockState(worldPosition).setValue(BlockStateProperties.POWERED, e > 0)
                ,3);}
        setChanged();
        if ( level.getGameTime() % 20 == 0 && e > 0) {
            //vawt's own sound
            level.playSound(null, worldPosition, FDSounds.wind_turbine_loop.get(), SoundSource.BLOCKS, 0.2f, 1);
        }
        sendOutPower();
    }

    private <E extends BlockEntity & IAnimatable> PlayState predicate(AnimationEvent<E> event) {
        event.getController().transitionLengthTicks = 0;
        event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.vawt.spin", true));
        return getBlockState().getValue(BlockStateProperties.POWERED) ? PlayState.CONTINUE : PlayState.STOP;

    }

    //Sending energy to neighbor blocks
    private void sendOutPower() {
        AtomicInteger capacity = new AtomicInteger(energyStorage.getEnergyStored());
        if (capacity.get() > 0) {
            Direction direction = Direction.DOWN;
            BlockEntity te = null;
            if (level != null) {
                te = level.getBlockEntity(worldPosition.relative(direction));
            }
            if (te instanceof FDKineticGeneratorBlockEntity) {
                boolean doContinue = te.getCapability(CapabilityEnergy.ENERGY, direction.getOpposite()).map(handler ->
                {if (handler.canReceive()) {
                    int received = handler.receiveEnergy(Math.min(capacity.get(), 500), false);
                    capacity.addAndGet(-received);
                    energyStorage.consumeEnergy(received);
                    setChanged();
                    return capacity.get() > 0;
                } else {
                    return true;
                }
                }).orElse(true);
                if (!doContinue) {
                    return;
                }
            }
        }
        currentEnergy.set(0,energyStorage.getEnergyStored());
        maxEnergy.set(0,energyStorage.getMaxEnergyStored());
    }

    public FDVawtBlockEntity(BlockPos pos, BlockState state)
    {
        super(BLOCK_ENTITY_TYPE,pos,state);
    }

    //NBT saving and loading
    @Nonnull
    @Override
    public CompoundTag save(@Nonnull  CompoundTag tag) {
        energy.ifPresent(h -> h.serializeNBT(tag));

        return super.save(tag);
    }

    @Override
    public void load(@Nonnull CompoundTag tag) {
        energy.ifPresent(h -> h.deserializeNBT(tag));
        super.load(tag);
    }


    private FDEnergyStorage createEnergy() {
        return new FDEnergyStorage(500, 500) {
            @Override
            protected void onEnergyChanged() {
                setChanged();
            }

            @Override
            public boolean canExtract(){
                return true;
            }

            @Override
            public boolean canReceive(){
                return false;
            }
        };
    }


    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
        if (cap == CapabilityEnergy.ENERGY) {
            boolean isValid = side == Direction.DOWN;
            return isValid?energy.cast():super.getCapability(cap, side);
        }
        return super.getCapability(cap, side);
    }


    @Override
    public void registerControllers(AnimationData data) {
        data.addAnimationController(new AnimationController(this, "controller", 0, this::predicate));
    }

    @Override
    public AnimationFactory getFactory() {
        return this.factory;
    }
}
